package com.basker.pisces.utils;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.lang.Nullable;
import org.springframework.util.Assert;

import com.basker.pisces.exception.ExceptionFactory;
import com.esotericsoftware.reflectasm.MethodAccess;

public class ReflectionUtils {

    private static Map<Class<?>, MethodAccess> ASM_METHOD_ACCESS_CACHE = new HashMap<>();

    /**
     * 反射创建对象
     *
     * @param clazz
     * @return
     */
    public static <T> T createNewInstance(Class<T> clazz) {
        Assert.notNull(clazz, "parameter 'clazz' is required");

        try {
            T newObject = (T) (clazz.newInstance());
            return newObject;
        } catch (InstantiationException | IllegalAccessException e) {
            throw ExceptionFactory.createRuntimeException(e);
        }
    }

    /**
     * 反射创建对象
     *
     * @param clazz
     * @param paraClazzs
     * @param paras
     * @return
     */
    public static <T> T createNewInstance(Class<T> clazz, Class<?>[] paraClazzs, Object... paras) {
        Assert.notNull(clazz, "parameter 'clazz' is required");

        try {
            Constructor<T> cons = clazz.getConstructor(paraClazzs);
            T newObject = cons.newInstance(paras);
            return newObject;
        } catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException
                | IllegalArgumentException | InvocationTargetException e) {
            throw ExceptionFactory.createRuntimeException(e);
        }

    }

    /**
     * 反射创建对象
     *
     * @param className
     * @return
     */
    public static <T> T createNewInstance(String className) {
        Assert.hasText(className, "parameter 'className' is required");

        @SuppressWarnings("unchecked")
        Class<T> cls = (Class<T>) ClassUtils.getClass(className);
        return createNewInstance(cls);
    }

    /**
     * 反射创建对象
     *
     * @param className
     * @param paraClazzs
     * @param paras
     * @return
     */
    public static <T> T createNewInstance(String className, Class<?>[] paraClazzs, Object... paras) {
        Assert.hasText(className, "parameter 'className' is required");

        @SuppressWarnings("unchecked")
        Class<T> cls = (Class<T>) ClassUtils.getClass(className);
        return createNewInstance(cls, paraClazzs, paras);
    }

    /**
     * 从类及其父类中查找字段
     *
     * @param clazz
     * @param fieldName
     * @return
     */
    public static Field findField(Class<?> clazz, String fieldName) {
        Assert.notNull(clazz, "parameter 'clazz' is required");
        Assert.hasText(fieldName, "parameter 'fieldName' is required");

        return org.springframework.util.ReflectionUtils.findField(clazz, fieldName);
    }

    /**
     * 获取类以及所有父类的声明字段
     *
     * @param clazz
     * @return
     */
    public static Field[] getAllDeclaredFields(Class<?> clazz) {
        Assert.notNull(clazz, "parameter 'clazz' is required");

        return getAllDeclaredFields(clazz, null);
    }

    /**
     * 获取类以及所有父类的声明字段
     *
     * @param clazz
     * @param filter
     * @return
     */
    public static Field[] getAllDeclaredFields(Class<?> clazz,
                                               @Nullable org.springframework.util.ReflectionUtils.FieldFilter filter) {
        Assert.notNull(clazz, "parameter 'clazz' is required");

        final List<Field> fields = new ArrayList<>(32);
        org.springframework.util.ReflectionUtils.doWithFields(clazz, fields::add, filter);
        return fields.toArray(new Field[fields.size()]);
    }

    /**
     * 获取asm反射的MethodAccess
     *
     * @param clazz
     * @return
     */
    public static MethodAccess getAsmMethodAccess(Class<?> clazz) {
        Assert.notNull(clazz, "parameter 'clazz' is required");

        MethodAccess methodAccess = ASM_METHOD_ACCESS_CACHE.get(clazz);

        if (methodAccess == null) {
            synchronized (ASM_METHOD_ACCESS_CACHE) {
                methodAccess = MethodAccess.get(clazz);
                ASM_METHOD_ACCESS_CACHE.put(clazz, methodAccess);
            }
        }

        return methodAccess;
    }

    /**
     * 反射获取当前类上声明的字段
     *
     * @param clazz
     * @param fieldName
     * @return
     */
    public static Field getDeclaredField(Class<?> clazz, String fieldName) {
        Assert.notNull(clazz, "parameter 'clazz' is required");
        Assert.hasText(fieldName, "parameter 'fieldName' is required");

        return getDeclaredField(clazz, fieldName, true);
    }

    /**
     * 反射获取当前类上声明的字段
     *
     * @param clazz
     * @param fieldName
     * @param throwError
     * @return
     */
    public static Field getDeclaredField(Class<?> clazz, String fieldName, boolean throwError) {
        Assert.notNull(clazz, "parameter 'clazz' is required");
        Assert.hasText(fieldName, "parameter 'fieldName' is required");

        try {
            return clazz.getDeclaredField(fieldName);
        } catch (NoSuchFieldException | SecurityException e) {
            if (throwError) {
                throw ExceptionFactory.createRuntimeException(e);
            } else {
                return null;
            }
        }
    }

    /**
     * 反射获取字段的值
     *
     * @param field
     * @param target
     * @return
     */
    public static Object getFieldValue(Field field, Object target) {
        Assert.notNull(field, "parameter 'field' is required");
        Assert.notNull(target, "parameter 'target' is required");

        boolean recoverAccesible = false;

        try {
            if (!field.isAccessible()) {
                field.setAccessible(true);
                recoverAccesible = true;
            }

            return field.get(target);
        } catch (IllegalArgumentException | IllegalAccessException e) {
            throw ExceptionFactory.createRuntimeException(e);
        } finally {
            if (recoverAccesible) {
                field.setAccessible(false);
            }
        }
    }

    /**
     * 反射获取方法
     *
     * @param clazz
     * @param methodName
     * @param throwError
     * @param paraClazzs
     * @return
     */
    public static Method getMethod(Class<?> clazz, String methodName, boolean throwError, Class<?>... paraClazzs) {
        Assert.notNull(clazz, "parameter 'clazz' is required");
        Assert.hasText(methodName, "parameter 'methodName' is required");

        try {
            return clazz.getMethod(methodName, paraClazzs);
        } catch (SecurityException | NoSuchMethodException e) {
            if (throwError) {
                throw ExceptionFactory.createRuntimeException(e);
            } else {
                return null;
            }
        }
    }

    /**
     * 反射获取方法
     *
     * @param clazz
     * @param methodName
     * @param paraClazzs
     * @return
     */
    public static Method getMethod(Class<?> clazz, String methodName, Class<?>... paraClazzs) {
        Assert.notNull(clazz, "parameter 'clazz' is required");
        Assert.hasText(methodName, "parameter 'methodName' is required");

        return getMethod(clazz, methodName, true, paraClazzs);
    }

    /**
     * 反射调用方法
     *
     * @param method
     * @param target
     * @param paras
     * @return
     */
    public static Object invokeMethod(Method method, Object target, Object... paras) {
        Assert.notNull(method, "parameter 'method' is required");
        Assert.notNull(target, "parameter 'target' is required");

        boolean recoverAccesible = false;

        try {
            if (!method.isAccessible()) {
                method.setAccessible(true);
                recoverAccesible = true;
            }

            return method.invoke(target, paras);
        } catch (IllegalArgumentException | IllegalAccessException | InvocationTargetException e) {
            throw ExceptionFactory.createRuntimeException(e);
        } finally {
            if (recoverAccesible) {
                method.setAccessible(false);
            }
        }

    }

    /**
     * 反射设置字段的值
     *
     * @param field
     * @param target
     * @param value
     */
    public static void setFieldValue(Field field, Object target, Object value) {
        Assert.notNull(field, "parameter 'field' is required");
        Assert.notNull(target, "parameter 'target' is required");

        boolean recoverAccesible = false;

        try {
            if (!field.isAccessible()) {
                field.setAccessible(true);
                recoverAccesible = true;
            }

            field.set(target, value);
        } catch (IllegalArgumentException | IllegalAccessException e) {
            throw ExceptionFactory.createRuntimeException(e);
        } finally {
            if (recoverAccesible) {
                field.setAccessible(false);
            }
        }
    }

}
